using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace Taco.Timeline
{
    public class TimelineClipDecorator : PlayableBehaviour
    {
        public Timeline Timeline { get; private set; }
        public Track Track { get; private set; }
        public Playable Handle { get; private set; }
        public Playable Root { get; private set; }
        public int Index { get; private set; }

        TimelineClipPlayable ClipPlayable;
        float Time;

        public void Initialize(Playable decorator, Playable root, Track track, int index)
        {
            Track = track;
            Timeline = track.Timeline;
            Handle = decorator;
            Root = root;
            Index = index;

            root.ConnectInput(index, decorator, 0);
            decorator.SetInputCount(1);
        }
        public void Connect(TimelineClipPlayable playable)
        {
            ClipPlayable = playable;
            Handle.ConnectInput(0, playable.Handle, 0);
            Handle.SetInputWeight(0, 1);
        }

        public override void PrepareFrame(Playable playable, FrameData info)
        {
            PrepareFrame(info.deltaTime);
        }

        public void PrepareFrame(float deltaTime)
        {
            int direction = deltaTime > 0 ? 1 : -1;
            if (Mathf.Abs(deltaTime) > Timeline.MinEvaluateDeltaTime)
            {
                float handleTime = (float)Handle.GetTime();
                while (Time != handleTime)
                {
                    float delta = direction * Timeline.MinEvaluateDeltaTime;
                    if(direction == 1)
                    {
                        delta = Mathf.Min(delta, handleTime - Time);
                    }
                    else
                    {
                        delta = Mathf.Max(delta, handleTime - Time);
                    }
                    Evaluate(delta);
                }
            }
            else
            {
                Evaluate(deltaTime);
            }
        }
        public void Evaluate(float deltaTime)
        {
            float parentTime = Time;
            if (parentTime < ClipPlayable.StartTime)
            {
                Root.SetInputWeight(Index, 0);
                ClipPlayable.SetTime(0);
            }
            else if (ClipPlayable.StartTime <= parentTime && parentTime <= ClipPlayable.EndTime)
            {
                float selfTime = parentTime - ClipPlayable.StartTime;
                float remainTime = ClipPlayable.EndTime - parentTime;
                ClipPlayable.SetTime(selfTime + ClipPlayable.ClipInTime);

                if (!Track.RuntimeMuted)
                {
                    if (selfTime < ClipPlayable.EaseInTime)
                    {
                        if (ClipPlayable.OtherEaseInTime > 0)
                        {
                            Root.SetInputWeight(Index, selfTime / ClipPlayable.EaseInTime);
                        }
                        else
                        {
                            Root.SetInputWeight(Index, 1);
                            Timeline.RootPlayable.SetInputWeight(Track.PlayableIndex, selfTime / ClipPlayable.EaseInTime);
                        }
                    }
                    else if (remainTime < ClipPlayable.EaseOutTime)
                    {
                        if (ClipPlayable.OtherEaseOutTime > 0)
                        {
                            Root.SetInputWeight(Index, remainTime / ClipPlayable.EaseOutTime);
                        }
                        else
                        {
                            Root.SetInputWeight(Index, 1);
                            Timeline.RootPlayable.SetInputWeight(Track.PlayableIndex, remainTime / ClipPlayable.EaseOutTime);
                        }
                    }
                    else
                    {
                        Root.SetInputWeight(Index, 1);
                        Timeline.RootPlayable.SetInputWeight(Track.PlayableIndex, 1);
                    }
                }
            }
            else if (parentTime > ClipPlayable.EndTime)
            {
                float selfTime = parentTime - ClipPlayable.StartTime;
                ClipPlayable.SetTime(selfTime + ClipPlayable.ClipInTime);
                switch (ClipPlayable.ExtraPolationMode)
                {
                    case ExtraPolationMode.None:
                        Root.SetInputWeight(Index, 0);
                        break;
                    case ExtraPolationMode.Hold:
                        break;
                }
            }
            Time += deltaTime;
        }
        public void UpdateMuteState()
        {
            Evaluate(0);
            ClipPlayable.UpdateMuteState();
        }
        public void Destroy()
        {
            ClipPlayable.OnDestroy();
        }

        public static TimelineClipDecorator Create(Playable root, Track track, int index)
        {
            var a = ScriptPlayable<TimelineClipDecorator>.Create(track.Timeline.PlayableGraph);
            var b = a.GetBehaviour();
            b.Initialize(a, root, track, index);
            return b;
        }
    }

    public class TimelineClipPlayable : PlayableBehaviour
    {
        #region Param
        public Timeline Timeline { get; private set; }
        public Track Track { get; private set; }
        public Playable Handle { get; private set; }
        public ExtraPolationMode ExtraPolationMode { get; private set; }
        public float StartTime { get; private set; }
        public float EndTime { get; private set; }
        public float OtherEaseInTime { get; private set; }
        public float OtherEaseOutTime { get; private set; }
        public float EaseInTime { get; private set; }
        public float EaseOutTime { get; private set; }
        public float ClipInTime { get; private set; }
        public float Duration { get; private set; }

        public bool Active;
        #endregion

        public TimelineClipPlayable() { }

        public virtual void Initialize(Clip clip, Playable owner, Timeline timeline)
        {
            Timeline = timeline;
            Track = clip.Track;
            Handle = owner;
            Handle.SetInputCount(1);
            ExtraPolationMode = clip.ExtraPolationMode;
            StartTime = clip.StartFrame / (float)Timeline.FrameRate;
            EndTime = clip.EndFrame / (float)Timeline.FrameRate;
            OtherEaseInTime = clip.OtherEaseInFrame / (float)Timeline.FrameRate;
            OtherEaseOutTime = clip.OtherEaseOutFrame / (float)Timeline.FrameRate;
            EaseInTime = clip.EaseInFrame / (float)Timeline.FrameRate;
            EaseOutTime = clip.EaseOutFrame / (float)Timeline.FrameRate;
            ClipInTime = clip.ClipInFrame / (float)Timeline.FrameRate;
            Duration = clip.Duration / (float)Timeline.FrameRate;
        }
        public virtual void SetTime(float time)//(0->max)
        {
            float currentTime = (float)Handle.GetTime();
            Handle.SetTime(time);
            
            if (Track.RuntimeMuted)
                return;

            if (time > currentTime) //+
            {
                if (!Active && currentTime <= 0 && time > 0)
                {
                    Active = true;
                    OnEnable();
                }
                if (Active && currentTime <= Duration && time > Duration)
                {
                    Active = false;
                    OnDisable();
                }
            }
            else//-
            {
                if (!Active && currentTime > Duration && time <= Duration)
                {
                    Active = true;
                    OnEnable();
                }
                if (Active && currentTime >= 0 && time <= 0)
                {
                    Active = false;
                    OnDisable();
                }
            }
        }
        public virtual void UpdateMuteState()
        {
            float currentTime = (float)Handle.GetTime();
            if (Active && Track.RuntimeMuted)
            {
                Active = false;
                OnDisable();
            }
            else if(!Active && 0 <= currentTime && currentTime <= Duration)
            {
                Active = true;
                OnEnable();
            }
        }
        public virtual void OnEnable()
        {
        }
        public virtual void OnDisable()
        {
        }
        public virtual void OnDestroy()
        {
        }

        public static T Create<T>(Clip clip, Timeline timeline) where T : TimelineClipPlayable, new()
        {
            var a = ScriptPlayable<T>.Create(timeline.PlayableGraph);
            var b = a.GetBehaviour();
            b.Initialize(clip, a, timeline);
            return b;
        }
    }
}